package com.hwxt.ghs.service;

import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.stream.Collectors;

import org.apache.commons.io.FileUtils;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.NumericDocValuesField;
import org.apache.lucene.document.TextField;
import org.apache.lucene.index.DirectoryReader;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.queryparser.classic.MultiFieldQueryParser;
import org.apache.lucene.queryparser.classic.QueryParser;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.Sort;
import org.apache.lucene.search.SortField;
import org.apache.lucene.search.TopDocs;
import org.apache.lucene.search.highlight.Formatter;
import org.apache.lucene.search.highlight.Highlighter;
import org.apache.lucene.search.highlight.QueryScorer;
import org.apache.lucene.search.highlight.Scorer;
import org.apache.lucene.search.highlight.SimpleHTMLFormatter;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
import org.eclipse.jgit.api.DiffCommand;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.diff.DiffEntry;
import org.eclipse.jgit.diff.DiffEntry.ChangeType;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectReader;
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.storage.file.FileRepositoryBuilder;
import org.eclipse.jgit.treewalk.CanonicalTreeParser;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.thymeleaf.util.StringUtils;
import org.wltea.analyzer.lucene.IKAnalyzer;

import com.hwxt.ghs.vo.Project;

@Service
public class IndexService {

    @Value("${lucene_directory}")
    private String lucene_directory;
    @Value("${pull_code_before_index}")
    private boolean pull_code_before_index = false;
    @Value("${git_bash_exe}")
    private String git_bash_exe;
    

    // 创建索引
    public void buildIndex(List<Project> projectList) {
        long begin = System.currentTimeMillis();        
        try {
            Analyzer analyser = new IKAnalyzer();
            IndexWriterConfig config = new IndexWriterConfig(analyser);    
            Path lucenePath = Paths.get(lucene_directory + "/index");
            FileUtils.forceMkdir(lucenePath.toFile());
            FSDirectory directory = FSDirectory.open(lucenePath);
            IndexWriter writer = new IndexWriter(directory, config);
            writer.deleteAll();
            if(pull_code_before_index){
                writeBashScript();
            }
            projectList.stream().parallel().forEach(project -> {
                // 更新本地代码
                if(pull_code_before_index){
                    try{
                        // 之前创建过索引只更新代码即可，没创建过索引要先将远程所有分支都拉取到本地再更新代码
                        if(project.getIndextime() > 0){
                            executeGitBashCommand(project.getPath(), lucene_directory + "/pull.sh");
                        } else {
                            executeGitBashCommand(project.getPath(),lucene_directory + "/branch.sh");
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
                try {
                    Repository repo = new FileRepositoryBuilder().setGitDir(new File(project.getPath() + "/.git")).build();
                    ObjectReader reader = repo.newObjectReader();
                    Git git = new Git(repo);
                    List<Ref> branches = git.branchList().call();  
                    ConcurrentHashMap<String,HashSet<String>> branchsCache = new ConcurrentHashMap<>();
                    CopyOnWriteArrayList<Document> docs = new CopyOnWriteArrayList<>();
                    branches.stream().forEach(ref -> {
                        buildBranchIndex(project, repo, reader, branchsCache, docs, ref ); 
                    });
                    for(String commitid:branchsCache.keySet()){
                        HashSet<String> branchs = branchsCache.get(commitid);
                        String branch = branchs.stream().collect(Collectors.joining(","));
                        for(Document doc: docs){
                            if(commitid.equals(doc.get("commitId"))) {
                                doc.add(new TextField("branchs",branch,Field.Store.YES));
                                break;
                            }
                        }
                    }
                    writer.addDocuments(docs);
                    writer.commit();
                    project.setCommits(docs.size());
                    project.setBranchs(branches.size());
                    System.out.println("count: " + docs.size());
                } catch (Exception e) {
                    e.printStackTrace();
                }
            });         
            writer.close();
            long indexsize = FileUtils.sizeOfDirectory(lucenePath.toFile());
            for(Project prj: projectList){
                prj.setIndexsize(indexsize);                
                prj.setIndextime(System.currentTimeMillis());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("buildIndex 方法总耗时: " + (System.currentTimeMillis() - begin));
    }
    // 将脚本写入磁盘
    private void writeBashScript(){
        File pull = new File(lucene_directory,"pull.sh");
        if(pull.exists() && pull.length()>0){
            System.out.println("pull.sh exists");
        } else {
            List<String> lines = new ArrayList<>();
            try {
                lines.add("#!/bin/bash");
                lines.add("git clean -xdf && git reset --hard");
                lines.add("git branch | grep -v '\\->' | while read br; do git checkout  \"$br\" && git pull --all; done");
                FileUtils.writeLines(pull, "UTF-8", lines);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        File branch = new File(lucene_directory,"branch.sh");
        if(branch.exists() && branch.length()>0){
            System.out.println("branch.sh exists");
        } else {
            List<String> lines = new ArrayList<>();
            try {
                lines.add("#!/bin/bash");
                lines.add("git clean -xdf && git reset --hard");
                lines.add("git branch -r | grep -v '\\->' | while read remote; do git branch --track \"${remote#origin/}\" \"$remote\"; done");
                lines.add("git branch | grep -v '\\->' | while read br; do git checkout  \"$br\" && git pull; done");
                FileUtils.writeLines(branch, "UTF-8", lines);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    // 执行gitbash 命令
    private void executeGitBashCommand(String cd,String command) {
        try{
            long begin = System.currentTimeMillis();
            String gitBashStr []= new String[]{ "\""+git_bash_exe+"\"", "--cd=\"" +cd +"\"", "\"" + command + "\"" };
            System.out.println("执行命令行:");
            for(String str:gitBashStr){
                System.out.print(str + " ");
            }
            Process process = Runtime.getRuntime().exec(gitBashStr);
            process.waitFor();
            System.out.println("\n耗时: " + (System.currentTimeMillis() - begin) + "ms");
        }catch(Exception e){
            e.printStackTrace();
        }
    }

    private void buildBranchIndex(Project project, Repository repo, ObjectReader reader, ConcurrentHashMap<String, HashSet<String>> branchsCache, CopyOnWriteArrayList<Document> docs, Ref ref) {
        
        long start = System.currentTimeMillis();
        String branch = ref.getName();
        try {
            Git git = new Git(repo);
            ObjectId branchId = repo.resolve(branch);                    
            Iterable<RevCommit> commits = git.log().add(branchId).call();
            List<RevCommit> commitList = new ArrayList<>();
            commits.forEach((RevCommit commit) ->{    
                commitList.add(commit);
            });
            // 将日期格式化成 2024-09-09 格式
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            branch = branch.replace("refs/heads/", "");
            for (int i=0;i<commitList.size();i++) {
                RevCommit commit = commitList.get(i);
                String message = commit.getFullMessage();
                String commitId = commit.getId().getName();
                PersonIdent authorIdent = commit.getCommitterIdent();
                String email = authorIdent.getEmailAddress();
                String author = authorIdent.getName();
                Date when = authorIdent.getWhen();
                String date = sdf.format(when);                        
                long commitTime = when.getTime();
                // commit.getTree()
                // 跳过 merge 和 cherry-pick 的记录
                if(commit.getParentCount()>1) {
                    // System.out.println("跳过 commitId:" + commitId + ", author:" + author + ", email:" +email + ", commitTime:" + date + ", message:" + message) ;
                    continue;
                }                            
                if(branchsCache.containsKey(commitId)){
                    branchsCache.get(commitId).add(branch);
                    continue;
                }
                
                HashSet<String> branchs = new HashSet<>();
                branchs.add(branch);
                branchsCache.put(commitId, branchs);
                Document doc = new Document();
                doc.add(new TextField("project", project.getName(), Field.Store.YES));
                doc.add(new TextField("branch",branch,Field.Store.YES));
                doc.add(new TextField("message", message, Field.Store.YES));
                doc.add(new TextField("commitId", commitId, Field.Store.YES));
                doc.add(new TextField("author", author, Field.Store.YES));
                doc.add(new TextField("email", email, Field.Store.YES));
                doc.add(new TextField("time",date,Field.Store.YES ));
                doc.add(new NumericDocValuesField("commitTime", commitTime));
                if(commit.getParentCount() ==1 ){
                    RevCommit parent = commit.getParent(0);
                    CanonicalTreeParser newTree = new CanonicalTreeParser();
                    newTree.reset(reader, commit.getTree());
                    DiffCommand diff = git.diff().setContextLines(102).setShowNameAndStatusOnly(true).setNewTree(newTree);
                    CanonicalTreeParser oldTree = new CanonicalTreeParser();
                    oldTree.reset(reader, parent.getTree());
                    diff.setOldTree(oldTree);
                    String change="";
                    List<DiffEntry> diffEntrys = diff.call();
                    if(diffEntrys.size()>100){
                        change = "变更超过100个文件";
                    } else {
                        for(DiffEntry de: diffEntrys){                            
                            String oldPath = de.getOldPath();
                            String path =de.getNewPath();
                            ChangeType changeType = de.getChangeType() ;
                            if(changeType==ChangeType.DELETE) {
                                change = change + ",删除 " + oldPath;
                            } else {
                                change = change + ",修改 " + path;
                            }
                        }
                    }
                    doc.add(new TextField("change",change,Field.Store.YES));
                }
                docs.add(doc);
            }
        } catch (Exception e) {
            e.printStackTrace();    
        } finally {
            long costTime = System.currentTimeMillis() - start;
            System.out.println("分支: " + branch + " 创建索引耗时: " + costTime);
        }
    }

    public List<Map<String, Object>> search(Integer max, String q) {
        // 1.创建分词器，对搜索的关键词进行分词使用，且分词器与创建索引时的保持一致
        Analyzer analyser = new IKAnalyzer();

        // 多域中查找关键字，在fileName域中查询和content域中查询，
        // QueryParser queryParser = new QueryParser("message",analyser);
        QueryParser queryParser = new MultiFieldQueryParser(new String[] { "project","branch","author","email","time", "commitId","change","message" }, analyser);
        List<Map<String, Object>> result = new ArrayList<>();
        //进行高亮显示的代码：如下：这个其实也就是对应着html的语句，以便在网页上显示
		// 最多查询100条数据
        try {
            // 3.设置搜索关键词
            Query query = queryParser.parse(q);            
            // 4.创建Directory目录对象，指定索引库的位置
            Directory dir = FSDirectory.open(Paths.get(lucene_directory + "/index"));
            // 5.创建输入流对象
            IndexReader indexReader = DirectoryReader.open(dir);
            // 6.创建搜索对象
            IndexSearcher indexSearcher = new IndexSearcher(indexReader);
            Sort sort = new Sort(new SortField("message", SortField.Type.SCORE), new SortField("commitTime", SortField.Type.LONG, true));
            // 7.搜索，并返回结果,10条数据即可
            TopDocs topDocs = indexSearcher.search(query, max,sort);
            // 获取查询到的结果集总数
            System.out.println("查询到的结果集总数: " + topDocs.totalHits);
            // 8.获取结果集
            ScoreDoc[] scoreDocs = topDocs.scoreDocs;
            Formatter formatter = new SimpleHTMLFormatter("<font color='red'>","</font>");
            Scorer scorer = new QueryScorer(query);
            Highlighter highlighter = new Highlighter(formatter,scorer);
            // 9.遍历结果集
            if (scoreDocs != null) {
                int index = 0;
                for (ScoreDoc scoreDoc : scoreDocs) { // 获取查询到的文档的唯一标识，文档id，这个id是Lucene在创建文档的时候自动分配的
                    int docId = scoreDoc.doc;
                    // 通过文档id，读取文档
                    Document doc = indexSearcher.doc(docId);
                    String message = highlighter.getBestFragment(analyser,"message",doc.get("message"));
                    if(StringUtils.isEmpty(message)) {
                        message = doc.get("message");
                    }
                    
                    // System.out.println("得分：" + scoreDoc.score);
                    Map<String, Object> map = new HashMap<>();
                    map.put("index",index++);
                    map.put("project", doc.get("project"));
                    map.put("branch",doc.get("branch"));
                    map.put("branchs", doc.get("branchs").split(","));
                    map.put("docId", docId);
                    map.put("commitId", doc.get("commitId"));
                    map.put("author", doc.get("author"));
                    map.put("email", doc.get("email"));
                    map.put("commitTime",doc.get("time"));
                    map.put("message", message);
                    String change = doc.get("change");
                    if(!StringUtils.isEmpty(change)){
                        change = change.substring(1);
                        String changes [] = change.split(",");
                        for(int i=0;i<changes.length;i++){
                            String ch = changes[i];
                            changes[i] = highlighter.getBestFragment(analyser,"change",ch);
                            if(StringUtils.isEmpty(changes[i])){
                                changes[i] = ch;
                            }
                        }
                        map.put("changes",changes );
                    }else{
                        map.put("changes", new String[]{""});
                    }
                    result.add(map);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;

    }

}
